package net.example.lesson6.concurrent;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;

public class BoundedHashSet<T> {
    private final Set<T> set;
    private final Semaphore sem;

    public BoundedHashSet(int bound) {
        this.set = Collections.synchronizedSet(new HashSet<>());
        sem = new Semaphore(bound);
    }

    public boolean add(T o) throws InterruptedException {
        /**
         * 一个计数信号量。从概念上讲，信号量维护了一个许可集。
         * 如有必要，在许可可用前会阻塞每一个 acquire()，然后再获取该许可。
         * 每个 release() 添加一个许可，从而可能释放一个正在阻塞的获取者。
         * 但是，不使用实际的许可对象，Semaphore 只对可用许可的号码进行计数，并采取相应的行动。
         *
         * 获得一项前，每个线程必须从信号量获取许可，从而保证可以使用该项。该线程结束后，将项返回到池中并将许可返回到该信号量，从而允许其他线程获取该项。
         * 注意，调用 acquire() 时无法保持同步锁，因为这会阻止将项返回到池中。
         * 信号量封装所需的同步，以限制对池的访问，这同维持该池本身一致性所需的同步是分开的。
         *
         * 将信号量初始化为 1，使得它在使用时最多只有一个可用的许可，从而可用作一个相互排斥的锁。
         * 这通常也称为二进制信号量，因为它只能有两种状态：一个可用的许可，或零个可用的许可。
         * 按此方式使用时，二进制信号量具有某种属性（与很多 Lock 实现不同），即可以由线程释放“锁”，而不是由所有者（因为信号量没有所有权的概念）。
         * 在某些专门的上下文（如死锁恢复）中这会很有用。
         *
         * 此类的构造方法可选地接受一个公平 参数。当设置为 false 时，此类不对线程获取许可的顺序做任何保证。
         * 特别地，闯入 是允许的，也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可，从逻辑上说，就是新线程将自己置于等待线程队列的头部。
         * 当公平设置为 true 时，信号量保证对于任何调用获取方法的线程而言，都按照处理它们调用这些方法的顺序（即先进先出；FIFO）来选择线程、获得许可。
         * 注意，FIFO 排序必然应用到这些方法内的指定内部执行点。
         * 所以，可能某个线程先于另一个线程调用了 acquire，但是却在该线程之后到达排序点，并且从方法返回时也类似。
         * 还要注意，非同步的 tryAcquire 方法不使用公平设置，而是使用任意可用的许可。
         *
         * 通常，应该将用于控制资源访问的信号量初始化为公平的，以确保所有线程都可访问资源。
         * 为其他的种类的同步控制使用信号量时，非公平排序的吞吐量优势通常要比公平考虑更为重要。
         *
         * 此类还提供便捷的方法来同时 acquire 和释放多个许可。小心，在未将公平设置为 true 时使用这些方法会增加不确定延期的风险。
         *
         * 内存一致性效果：线程中调用“释放”方法（比如 release()）之前的操作 happen-before 另一线程中紧跟在成功的“获取”方法（比如 acquire()）之后的操作。
         *
         * 从以下版本开始：
         */
        sem.acquire();
        boolean wasAdded = false;
        try {
            wasAdded = set.add(o);
            return wasAdded;
        }finally {
            if(!wasAdded) {
                sem.release();
            }
        }
    }

    public boolean remove(T o) {
        boolean wasRemoved = set.remove(o);
        if (wasRemoved) {
            sem.release();
        }
        return wasRemoved;
    }


    public static void main(String[] args) {
        BoundedHashSet<Integer> bhs = new BoundedHashSet(5);

        for (int i = 0; i < 10; i++) {
            final int num = i;
            Thread t = new Thread(() ->{
                try {
                    bhs.add(num);
                    System.out.println(String.format("线程%d添加元素", num));

//                    Thread.sleep(num * 1000);
                    bhs.remove(num);
                    System.out.println(String.format("线程%d删除元素", num));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            t.start();
        }
    }
}
